import sys
import os
import copy
import logging

sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))

from command_parser.clang_parse.clang_command_parser import ClangParser
from utils.common_utils import *
from configure.compiler_configure import *
from base.context import Context
from utils.db_object_pool import DBObjectPool
from configure.config_logging import capture_log_init


class ClangHandler(object):
    def __init__(self, command=[]):
        self.command = command
        self.env = {}
        self.cwd = ""
        self.context = Context()
        self.capture_db_handler = None
        self.cache_db_handler = None
    
    def init_env(self):
        self.cwd = os.getcwd()
        self.env = dict(os.environ)
        remove_envs_by_keys(env=self.env, remove_list=["LD_PRELOAD"])
        self.context.update_work_path(os.path.dirname(self.env["IRGEN_WORK_PATH"]))
        self.init_logger()
        self.context.check_redis_cached_config()
        self.capture_db_handler = DBObjectPool.get_db_inst_by_name("clang")
    
    def init_logger(self):
        capture_log_init(log_dir=self.context.LOG_DIR, log_mode="a", log_name="clang.log", log_level="DEBUG")
        
    def run_clang_handler(self):
        logging.debug("capture clang command: %s" % ' '.join(self.command))
        parser = ClangParser(command=self.command, context=self.context)
        compiler, options = parser.parse_command()
        action_type = parser.get_action_type()
        
        if action_type == PREPROCESS:
            command_line = parser.get_original_command()
            ret = run_command(command=command_line, env=self.env)
            if ret:
                logging.warning("gcc command failed: %s, Error Code(%d)" % (' '.join(command_line), ret))
            else:
                logging.debug("capture preprocess command: %s" % (' '.join(command_line)))
            return ret
        
        elif action_type == COMPILE:
            inputs = parser.get_input_options()
            inputs_len = len(inputs)
            # print("input_options:" + ' '.join([' '.join(opt.get_option()) for opt in inputs]))
            logging.debug("compiler command: " + ' '.join([' '.join(opt.get_option()) for opt in parser.options]))
            for pop_index in range(inputs_len):
                local_options = copy.deepcopy(options)
                local_parser = ClangParser(command="", context=self.context, compiler=compiler, options=local_options,
                                         cwd=os.getcwd())
                local_inputs = local_parser.get_input_options()
                # print("delete input option:" + ' '.join(local_inputs[pop_index].get_option()))
                local_inputs.pop(pop_index)
                local_parser.delete_options_by_list(remove_list=local_inputs)
                # local_output = local_parser.get_output_options()
                # print("compile command before devide: " + ' '.join([' '.join(opt.get_option()) for opt in local_parser.options]))
                local_command = local_parser.get_divided_compile_command()
                # print("compile command before devide: " + ' '.join([' '.join(opt.get_option()) for opt in local_parser.options]))
                # active_cache_env(env=self.env, context=self.context)
                ret = run_reassemble_command(command=local_command, env=self.env)
                if ret is not 0:
                    logging.warning("clang command failed: %s, Error Code(%d)" % (' '.join(local_command), ret))
                    return ret
                else:
                    logging.debug("Record command: {}".format(" ".join(local_command)))
                    # print("[INFO] Capture Clang Command: %s" % ' '.join(local_command))
                self.record_command(parser=local_parser)
                
        elif action_type == COMPILE_LINK:
            
            inputs = parser.get_input_options()
            outputs = parser.get_output_options()
            inputs_len = len(inputs)
            parser.delete_options_by_list(remove_list=outputs)
            logging.debug("compiler link command: " + [opt.get_option() for opt in parser.options])
            for pop_index in range(inputs_len):
                
                if not parser.check_is_src_file(inputs[pop_index]):
                    continue
                
                local_options = copy.deepcopy(options)
                local_parser = ClangParser(context=self.context, clang=compiler, options=local_options, 
                                         cwd=os.getcwd())
                local_inputs = local_parser.get_input_options()
                local_inputs.pop(pop_index)
                local_parser.delete_options_by_list(local_inputs)
                local_command = local_parser.get_divided_compile_command()
                local_output = local_parser.get_output_options()
                inputs[pop_index].parameter = local_output[0].parameter
                # active_cache_env(env=self.env, context=self.context)
                ret = run_reassemble_command(command=local_command, env=self.env)
                if ret != 0:
                    logging.warning("gcc command failed: %s, Error Code(%d)" % (' '.join(command_line), ret))
                    return ret
                else:
                    logging.debug("Record command: {}".format(" ".join(local_command)))
                    # print("[INFO] Capture Clang Command: %s" % ' '.join(local_command))
                self.record_command(parser=local_parser)
                
            parser.extend_options_by_list(extend_list=outputs)
            command_line = parser.get_divided_link_command()
            ret = run_reassemble_command(command_line, env=self.env)
        
        elif action_type == LINK:
            command_line = parser.get_original_command()
            active_capture_env(env=self.env, context=self.context)
            logging.debug("capture link command %s" % ' '.join(command_line))
            run_command(command=command, env=self.env)
        return 0
    
    def record_command(self, parser=None, cwd=""):
        if parser is None:
            logging.error("command record failed: parser is not initial")
            return
        
        parser.resolve_symbolic_inputs()
        outputs = parser.get_output_options()
        inputs = parser.get_input_options()
        
        
        command = parser.get_original_command()
        if not outputs or not outputs[0].parameter:
            raise Exception("Command: %s without any output" % ' '.join(command))
            
        output = outputs[0].parameter[0]
        real_output = real_file_path(output, parser.cwd)
        dependencies = parser.parse_dep_files()
        
        self.save_dep_info(name=real_output, command=command, input_files=dependencies)
        
    
    def save_dep_info(self, name="", command=[], input_files=[]):
        output_md5 = file_md5(name)
        
        self.capture_db_handler.set_name_dep(name_md5=output_md5, name=name)
        dep_files_md5 = []
        for dep_file in input_files:
            dep_file = real_file_path(dep_file)
            dep_file_md5 = file_md5(dep_file)
            if dep_file_md5:
                dep_files_md5.append(dep_file_md5)
                self.capture_db_handler.set_name_dep(name_md5=dep_file_md5, name=dep_file)

        self.capture_db_handler.set_link_dep(name_md5=output_md5, links=dep_files_md5)
        self.capture_db_handler.set_cmd_dep(name_md5=output_md5, cmd=[self.cwd, command])
    
if __name__ == "__main__":
   command = sys.argv[2:]
   handler = ClangHandler(command)
   
   handler.init_env()
   handler.run_clang_handler()